﻿using System;
using System.Collections.Generic;
using Simp.CodeModel.Exceptions;

namespace Simp.CodeModel.CoreVisitors
{
	/// <summary>Visitor visiting a function tree and binding function calls.</summary>
	public class BindFunctionVisitor : IFunctionVisitor
	{
		private readonly IDictionary<string, IFunction> _functionIndex;
		private readonly BindingFailureMode _bindingFailureMode;
		private readonly string[] _unknownFunctionNameList;

		/// <summary>Construct a visitor with an index of known functions.</summary>
		/// <param name="functionIndex"></param>
		/// <param name="bindingFailureMode">Failure mode when meeting an unknown function.</param>
		/// <param name="unknownFunctionNameList">List of unknown functions:  they won't raise exceptions.</param>
		public BindFunctionVisitor(
			IDictionary<string, IFunction> functionIndex,
			BindingFailureMode bindingFailureMode,
			params string[] unknownFunctionNameList)
		{
			if (functionIndex == null)
			{
				throw new ArgumentNullException("functionIndex");
			}
			_functionIndex = functionIndex;
			_bindingFailureMode = bindingFailureMode;
			_unknownFunctionNameList = unknownFunctionNameList;
		}

		#region IFunctionVisitor Members
		IFunction IFunctionVisitor.Visit(IFunction function)
		{
			UnboundFunctionCalling unbound = function as UnboundFunctionCalling;

			if (unbound == null)
			{
				IFunction visitedFunction = function.AcceptVisitor(this);

				return visitedFunction;
			}
			else
			{
				return BindFunction(unbound);
			}
		}
		#endregion

		private IFunction BindFunction(UnboundFunctionCalling unbound)
		{
			IFunction functionToCall;

			if (_functionIndex.TryGetValue(unbound.FunctionName, out functionToCall))
			{
				if (functionToCall.ParameterNames.Length != 0 && unbound.FunctionParameters.Length == 0)
				{	//	We are only refering to a function without calling it
					return new FunctionReference(functionToCall);
				}
				else if (unbound.FunctionParameters.Length == 0)
				{	//	We have a simple function substitution
					return functionToCall;
				}
				else
				{	//	Applying parameters to a function
					IFunction[] parameters = FunctionHelper.CreateParameters(functionToCall, unbound.FunctionParameters);
					BoundFunctionCalling bound = new BoundFunctionCalling(functionToCall, parameters);

					return FunctionHelper.ApplyVisitor(bound, this);
				}
			}
			else if (_bindingFailureMode == BindingFailureMode.Fail
				&& !((IList<string>)_unknownFunctionNameList).Contains(unbound.FunctionName))
			{
				throw new UnknownFunctionException(unbound.FunctionName);
			}
			else if (_bindingFailureMode == BindingFailureMode.TolerateParameter
				&& !unbound.IsParameter
				&& !((IList<string>)_unknownFunctionNameList).Contains(unbound.FunctionName))
			{
				throw new UnknownFunctionException(unbound.FunctionName);
			}
			else
			{
				IFunction subFunctionsVisited = ((IFunction)unbound).AcceptVisitor(this);

				return subFunctionsVisited;
			}
		}
	}
}